Lambdaでスポットリクエストのキャンセルをしてみた
はじめに
こんにちは、アノテーションのなかたです。
今回は、Lambdaでスポットリクエストのキャンセルをしてみました。
今回やりたいこと
Lambdaからスポットリクエストのキャンセルするにあたって、前提とする環境についてです。
以下の図のように、起動テンプレートによってスポットリクエストが作成されている環境になります。
(スポットリクエストが起動テンプレートによる作成でなくとも、今回の検証ではほとんどの場合適用できるではないかなと思います。)
ここから、スポットリクエストとスポットインスタンスを削除したいというのが今回の目的です。
そのため、Lambdaから削除する方法を検証してみました。
やってみる
1. Lambda関数の作成
Python3.12環境で関数を作成します。
関数の流れとしては、
- Nameタグから対象のスポットリクエストを見つける
- スポットリクエストに紐づいているスポットインスタンスを見つける
- それぞれを削除する
のようになります。
そのため、コードとしては以下のようになりました。
ソースコード
import boto3
import json
import os
from botocore.exceptions import ClientError
def lambda_handler(event, context):
# 環境変数からリージョンを取得、設定されていない場合はデフォルト値を使用
region = os.environ.get('AWS_REGION', 'ap-northeast-1')
# EC2クライアントの初期化
ec2_client = boto3.client('ec2', region_name=region)
# 環境変数からスポットリクエストのNameタグを取得
spot_request_name = os.environ.get('SPOT_REQUEST_NAME')
if not spot_request_name:
return {
'statusCode': 400,
'body': '環境変数 SPOT_REQUEST_NAME が設定されていません。'
}
try:
# 指定されたNameタグを持つスポットリクエストを検索
spot_requests = ec2_client.describe_spot_instance_requests(
Filters=[
{'Name': 'tag:Name', 'Values': [spot_request_name]},
{'Name': 'state', 'Values': ['active', 'open']}
]
)
if not spot_requests['SpotInstanceRequests']:
return {
'statusCode': 404,
'body': '指定されたNameタグを持つアクティブなスポットリクエストが見つかりませんでした。'
}
# 複数のスポットリクエストに対応するため、スポットリクエストIDとインスタンスIDを取得
spot_request_ids = []
instance_ids = []
for request in spot_requests['SpotInstanceRequests']:
spot_request_ids.append(request['SpotInstanceRequestId'])
if 'InstanceId' in request:
instance_ids.append(request['InstanceId'])
# スポットリクエストのキャンセル
cancel_response = ec2_client.cancel_spot_instance_requests(
SpotInstanceRequestIds=spot_request_ids
)
# インスタンスの終了
if instance_ids:
terminate_response = ec2_client.terminate_instances(
InstanceIds=instance_ids
)
# キャンセルと終了の成功を確認
cancelled_ids = [req['SpotInstanceRequestId'] for req in cancel_response['CancelledSpotInstanceRequests']]
terminated_ids = instance_ids if instance_ids else []
return {
'statusCode': 200,
'body': json.dumps({
'CancelledSpotRequests': cancelled_ids,
'TerminatedInstances': terminated_ids
})
}
except ClientError as e:
# エラーハンドリング
error_message = e.response['Error']['Message']
return {
'statusCode': 500,
'body': f'エラーが発生しました: {error_message}'
}
こちらのスクリプトは、Claude 3.5 Sonnetを使用して作成し、一部を私が修正しました。
こちらのコードをデプロイします。
2. 環境変数の設定
Lambda関数の実行に使用する環境変数を次のように設定します。
スポットリクエストを特定するため、Nameタグの値を設定します。
東京リージョン以外を指定したい場合
以下の環境変数も追加します。
AWS_REGION: リージョン名
3. 実行ロールの変更
以下のJSONをインラインポリシーとして、Lambda関数の実行ロールに追加します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeSpotInstanceRequests",
"ec2:CancelSpotInstanceRequests",
"ec2:TerminateInstances"
],
"Resource": "*"
}
]
}
それぞれ Action の目的は次のようになります。
ec2:DescribeInstances
スポットインスタンスを見つけるためec2:DescribeSpotInstanceRequests
スポットリクエストを見つけるためec2:CancelSpotInstanceRequests
スポットリクエストをキャンセルするためec2:TerminateInstances
スポットインスタンスを削除 / 終了するため
4. 検証環境の準備
以下の図のように、検証環境を準備していきます。
スポットリクエストを行う起動テンプレートの作成手順については、以下の記事をを参照しています。
- まず、スポットリクエストを行う起動テンプレートからスポットインスタンスを起動します。
- 起動テンプレートのリソースタグからリソースタイプとして
スポットインスタンスリクエスト
を選択します。
これにより、次のようにスポットリクエストにNameタグを付与するよう設定できます。
作成されたリソースの確認をします。
起動テンプレートからインスタンスを作成し、スポットリクエストが作成されます。
また、インスタンスも起動しています。
5. 実行
では、環境の準備が完了したため、作成したLambda関数を実行します。
Response
{
"statusCode": 200,
"body": "{\"CancelledSpotRequests\": [\"sir-g1gps1nm\"], \"TerminatedInstances\": [\"i-093cec4bfa3104366\"]}"
}
正常に実行が完了したようです。
スポットリクエストの画面でも確認します。
スポットリクエストの状態がcanceled
となっているのが確認できました。
また、スポットインスタンスも削除されていることが確認できました。
まとめ
今回の検証から、私なりに重要な部分をまとめてみました。
- Lambda からスポットリクエストをキャンセルする場合は、スポットインスタンスの終了も実行する必要がある
- 起動テンプレートからスポットリクエストにタグを付与するには、リソースタグのリソースタイプとして
スポットインスタンスリクエスト
を選択する - Lambda関数で他のリソースを操作する場合は、実行ロール権限の確認が重要である
参考
アノテーション株式会社について
アノテーション株式会社はクラスメソッドグループのオペレーション専門特化企業です。サポート・運用・開発保守・情シス・バックオフィスの専門チームが、最新 IT テクノロジー、高い技術力、蓄積されたノウハウをフル活用し、お客様の課題解決を行っています。当社は様々な職種でメンバーを募集しています。「オペレーション・エクセレンス」と「らしく働く、らしく生きる」を共に実現するカルチャー・しくみ・働き方にご興味がある方は、アノテーション株式会社 採用サイトをぜひご覧ください。